package com.wish233.wiliwilivideo.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import com.jd.platform.hotkey.client.callback.JdHotKeyStore;
import com.wish233.wiliwilivideo.domain.dto.likes.LikeCreateRequest;
import com.wish233.wiliwilivideo.domain.dto.likes.LikeQueryRequest;
import com.wish233.wiliwilivideo.domain.po.Likes;
import com.wish233.wiliwilivideo.mapper.LikeCountsMapper;
import com.wish233.wiliwilivideo.mapper.LikesMapper;
import com.wish233.wiliwilivideo.mq.MqProducer;
import com.wish233.wiliwilivideo.service.LikesService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import static com.wish233.wiliwilicommon.constants.RedisConstants.LIKE_COUNT_KEY;
import static com.wish233.wiliwilicommon.constants.RedisConstants.LIKE_SET_KEY;

/**
 * @author WHH
 * @description 针对表【likes(点赞记录表)】的数据库操作Service实现
 * @createDate 2025-03-18 14:14:24
 */
@Service
public class LikesServiceImpl extends ServiceImpl<LikesMapper, Likes>
        implements LikesService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private LikeCountsMapper likeCountsMapper;
    @Resource
    private MqProducer mqProducer;
    @Resource
    private Cache<String, Long> likeCountCache;
    @Resource
    private Cache<String, Set<String>> likeSetCache;

    @Override
    public Long recordLike(LikeCreateRequest likeCreateRequest) {
        String userId = (String) StpUtil.getLoginId();
        Integer bizId = likeCreateRequest.getBizId();
        Long messageId = likeCreateRequest.getMessageId();
        String likeSetKey = LIKE_SET_KEY + bizId + ":" + messageId;
        String likeCountKey = LIKE_COUNT_KEY + bizId + ":" + messageId;
        String hotLikeKey = "hotLike__" + bizId + "_" + messageId;
        // 检查是否需要分片
        Long setSize = stringRedisTemplate.opsForSet().size(likeSetKey);
        if (setSize != null && setSize > 10000) {
            // 计算分片key
            int shardNum = Math.abs(userId.hashCode()) % 10;
            likeSetKey = LIKE_SET_KEY + bizId + ":" + messageId + ":shard_" + shardNum;
        }
        // 1. 检查是否是热点key
        if (JdHotKeyStore.isHotKey(hotLikeKey)) {
            // 2. 从本地缓存获取点赞集合
            String finalLikeSetKey = likeSetKey;
            Set<String> likeSet = likeSetCache.get(hotLikeKey, k -> {
                // 如果本地缓存没有，从Redis加载
                Set<String> members = stringRedisTemplate.opsForSet().members(finalLikeSetKey);
                return members != null ? members : new HashSet<>();
            });
            // 3. 检查用户是否已点赞
            boolean isLiked = likeSet.contains(userId);
            // 4. 更新本地缓存
            if (isLiked) {
                likeSet.remove(userId);
            } else {
                likeSet.add(userId);
            }
            likeSetCache.put(hotLikeKey, likeSet);
            // 5. MQ更新到Redis
            mqProducer.sendLike2Mq4Redis(isLiked, userId, bizId, messageId);
            // 6. 返回本地计算的点赞数
            Long localCount = likeCountCache.getIfPresent(hotLikeKey);
            if (localCount == null) {
                localCount = stringRedisTemplate.opsForValue().increment(likeCountKey, isLiked ? -1L : 1L);
            } else {
                if (isLiked) {
                    localCount -= 1;
                } else {
                    localCount += 1;
                }
                likeCountCache.put(hotLikeKey, localCount);
            }
            return localCount != null ? localCount : 0L;
        }
        // 非热点key走Lua脚本
        String luaScript =
                "local isMember = redis.call('sismember', KEYS[1], ARGV[1])\n" +
                        "local count = 0\n" +
                        "if isMember == 1 then\n" +
                        "    redis.call('srem', KEYS[1], ARGV[1])\n" +
                        "    count = redis.call('decr', KEYS[2])\n" +
                        "    redis.call('publish', 'like_event', 'REMOVE')\n" +
                        "    return count\n" +
                        "else\n" +
                        "    redis.call('sadd', KEYS[1], ARGV[1])\n" +
                        "    count = redis.call('incr', KEYS[2])\n" +
                        "    redis.call('publish', 'like_event', 'ADD')\n" +
                        "    return count\n" +
                        "end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(luaScript);
        redisScript.setResultType(Long.class);
        Long newCount = stringRedisTemplate.execute(
                redisScript,
                Arrays.asList(likeSetKey, likeCountKey),
                userId
        );
        // 发送MQ
        Long oldCount = Optional.ofNullable(stringRedisTemplate.opsForValue().get(likeCountKey))
                .map(Long::parseLong).orElse(0L);
        if (newCount != null && !newCount.equals(oldCount)) {
            if (newCount > oldCount) {
                mqProducer.sendLikeOperationMessage("ADD", userId, bizId, messageId);
                mqProducer.sendLikeCountMessage(bizId, messageId, 1L);
            } else {
                mqProducer.sendLikeOperationMessage("REMOVE", userId, bizId, messageId);
                mqProducer.sendLikeCountMessage(bizId, messageId, -1L);
            }
        }
        return newCount != null ? newCount : 0L;
    }

    @Override
    public Long queryLikeNum(LikeQueryRequest likeQueryRequest) {
        Integer bizId = likeQueryRequest.getBizId();
        Long messageId = likeQueryRequest.getMessageId();
        String hotKey = "hotVideo__" + messageId;
        //是热key从缓存返回
        if (bizId == 1 && JdHotKeyStore.isHotKey(hotKey)) {
            String hotVideoLikesKey = "hotVideoLikes__" + messageId;
            Object o = JdHotKeyStore.get(hotVideoLikesKey);
            if (o != null) {
                return (long) o;
            }
        }
        //先查redis
        String likeCountKey = LIKE_COUNT_KEY + bizId + ":" + messageId;
        String str = stringRedisTemplate.opsForValue().get(likeCountKey);
        if (StrUtil.isBlank(str)) {
            //Redis为空再查数据库
            Long count = likeCountsMapper.queryLikeCount(bizId, messageId);
            if (count == null) {
                return 0L;
            } else {
                stringRedisTemplate.opsForValue().set(likeCountKey, String.valueOf(count));
                return count;
            }
            //数据库也为空
        } else {
            return Long.parseLong(str);
        }
    }

    @Override
    public boolean isUserLiked(String userId, Integer bizId, Long messageId) {
        String likeSetKey = LIKE_SET_KEY + bizId + ":" + messageId;
        Boolean isMember = stringRedisTemplate.opsForSet().isMember(likeSetKey, userId);
        return BooleanUtil.isTrue(isMember);
    }
}




